import { $set } from '@feng3d/serialization';
import { AmbientLight3D, Color, CubeTexture3D, CubeTextureLoader3D, DirectionalLight3D, Mesh3D, MeshBasicMaterial3D, MeshLambertMaterial3D, MeshPhongMaterial3D, Node3D, PerspectiveCamera3D, Scene3D, TextureLoader3D, WebGLRenderer3D } from 'feng3d-three';
import { Material3D } from 'feng3d-three';
import GUI from 'lil-gui';
import '../../extension/controls/OrbitControls3D';
import { OrbitControls3D } from '../../extension/controls/OrbitControls3D';
import { TeapotGeometry3D } from '../../extension/geometries/TeapotGeometry3D';

// import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

// import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js';

const rootPath = '../../';

let camera: PerspectiveCamera3D;
let scene: Scene3D;
let renderer: WebGLRenderer3D;
let cameraControls: OrbitControls3D;
let effectController: {
	newTess: number,
	bottom: boolean,
	lid: boolean,
	body: boolean,
	fitLid: boolean,
	nonblinn: boolean,
	newShading: 'glossy'
};
const teapotSize = 300;
let ambientLight: AmbientLight3D;
let light: DirectionalLight3D;

let tess = -1;	// force initialization
let bBottom: boolean;
let bLid: boolean;
let bBody: boolean;
let bFitLid: boolean;
let bNonBlinn: boolean;
let shading: string;

let teapot: Mesh3D;
let textureCube: CubeTexture3D;
const materials: { [name: string]: Material3D } = {};

init();
render();

function init()
{
	const container = document.createElement('div');
	document.body.appendChild(container);

	const canvasWidth = window.innerWidth;
	const canvasHeight = window.innerHeight;

	// CAMERA
	camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 45, aspect: window.innerWidth / window.innerHeight, near: 1, far: 80000 });
	camera.entity.position.set(-600, 550, 1300);

	// LIGHTS
	ambientLight = new Node3D().addComponent('AmbientLight3D', { color: { value: 0x333333 } });

	light = new Node3D().addComponent('DirectionalLight3D', { color: { value: 0xFFFFFF }, intensity: 1.0 });
	light.entity.position.set(0.32, 0.39, 0.7);

	// RENDERER
	renderer = new Node3D().addComponent('WebGLRenderer3D', { parameters: { antialias: true } });
	renderer.setPixelRatio(window.devicePixelRatio);
	renderer.setSize(canvasWidth, canvasHeight);
	renderer.outputEncoding = 'sRGBEncoding';
	container.appendChild(renderer.domElement);

	// EVENTS
	window.addEventListener('resize', onWindowResize);

	// CONTROLS
	cameraControls = new Node3D().addComponent('OrbitControls3D', { camera, domElement: renderer.domElement });
	cameraControls.emitter.on('change', render);

	// TEXTURE MAP
	const textureMap = new TextureLoader3D().load(`${rootPath}textures/uv_grid_opengl.jpg`);
	textureMap.wrapS = textureMap.wrapT = 'RepeatWrapping';
	textureMap.anisotropy = 16;
	textureMap.encoding = 'sRGBEncoding';

	// REFLECTION MAP
	const path = `${rootPath}textures/cube/pisa/`;
	const urls = ['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png'];

	textureCube = new CubeTextureLoader3D().setPath(path).load(urls);
	textureCube.encoding = 'sRGBEncoding';

	materials['wireframe'] = new MeshBasicMaterial3D({ wireframe: true });
	materials['flat'] = $set(new MeshPhongMaterial3D(), { specular: { value: 0x000000 }, flatShading: true, side: 'DoubleSide' });
	materials['smooth'] = $set(new MeshLambertMaterial3D(), { side: 'DoubleSide' });
	materials['glossy'] = $set(new MeshPhongMaterial3D(), { side: 'DoubleSide' });
	materials['textured'] = $set(new MeshPhongMaterial3D(), { map: textureMap, side: 'DoubleSide' });
	materials['reflective'] = $set(new MeshPhongMaterial3D(), { envMap: textureCube, side: 'DoubleSide' });

	// scene itself
	scene = renderer.addComponent('Scene3D');
	scene.background = new Color(0xAAAAAA);

	scene.entity.addChild(ambientLight.entity);
	scene.entity.addChild(light.entity);

	// GUI
	setupGui();
}

// EVENT HANDLERS

function onWindowResize()
{
	const canvasWidth = window.innerWidth;
	const canvasHeight = window.innerHeight;

	renderer.setSize(canvasWidth, canvasHeight);

	camera.aspect = canvasWidth / canvasHeight;
	camera.updateProjectionMatrix();

	render();
}

function setupGui()
{
	effectController = {
		newTess: 15,
		bottom: true,
		lid: true,
		body: true,
		fitLid: false,
		nonblinn: false,
		newShading: 'glossy'
	};

	const gui = new GUI();
	gui.add(effectController, 'newTess', [2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50]).name('Tessellation Level').onChange(render);
	gui.add(effectController, 'lid').name('display lid').onChange(render);
	gui.add(effectController, 'body').name('display body').onChange(render);
	gui.add(effectController, 'bottom').name('display bottom').onChange(render);
	gui.add(effectController, 'fitLid').name('snug lid').onChange(render);
	gui.add(effectController, 'nonblinn').name('original scale').onChange(render);
	gui.add(effectController, 'newShading', ['wireframe', 'flat', 'smooth', 'glossy', 'textured', 'reflective']).name('Shading').onChange(render);
}

//

function render()
{
	if (effectController.newTess !== tess
		|| effectController.bottom !== bBottom
		|| effectController.lid !== bLid
		|| effectController.body !== bBody
		|| effectController.fitLid !== bFitLid
		|| effectController.nonblinn !== bNonBlinn
		|| effectController.newShading !== shading)
	{
		tess = effectController.newTess;
		bBottom = effectController.bottom;
		bLid = effectController.lid;
		bBody = effectController.body;
		bFitLid = effectController.fitLid;
		bNonBlinn = effectController.nonblinn;
		shading = effectController.newShading;

		createNewTeapot();
	}

	// skybox is rendered separately, so that it is always behind the teapot.
	if (shading === 'reflective')
	{
		scene.background = textureCube;
	}
	else
	{
		scene.background = null;
	}

	renderer.render(scene, camera);
}

// Whenever the teapot changes, the scene is rebuilt from scratch (not much to it).
function createNewTeapot()
{
	if (teapot !== undefined)
	{
		teapot.geometry.dispose();
		scene.entity.removeChild(teapot.entity);
	}

	const geometry = $set(new TeapotGeometry3D(), {
		size: teapotSize,
		segments: tess,
		bottom: effectController.bottom,
		lid: effectController.lid,
		body: effectController.body,
		fitLid: effectController.fitLid,
		blinn: !effectController.nonblinn as any
	});

	teapot = new Node3D().addComponent('Mesh3D', {
		geometry, material: materials[shading]
	});

	scene.entity.addChild(teapot.entity);
}
